/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2019年9月30日
 * V4.0
 */
package com.jphenix.kernel.script;

import com.jphenix.driver.cluster.ClusterFilter;
import com.jphenix.driver.cluster.IClusterTrigger;
import com.jphenix.driver.cluster.ServerInfoVO;
import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.lang.SListMap;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.exceptions.HttpException;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;
import com.jphenix.standard.viewhandler.IViewHandler;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 远程脚本加载器
 * 
 * 注意：
 * 
 * 			关于不同服务器脚本名相同的问题：如果要调用一个脚本，这个脚本名本地也存在，就只会调用本地的这个脚本。
 *                                          通常要避免不同系统脚本同名，如果万一真有这种同名情况，而且必须调用
 *                                          远程服务器中的同名脚本，就在调用时指定群组的名字，比如：<%@other_group(脚本代码)%>
 * 
 * 
 * 主要作用：
 * 
 * 1. 同一个集群分组中的脚本代码自动同步
 * 2. 直接调用不同群组脚本代码（支持路由调用）
 * 
 * 
 * 注意：如果并行集群中，脚本根文件夹是多台服务器共享的，就不要启用脚本路由。因为脚本路由针对
 *       同群组集群，本身就带脚本变动共享。如果脚本文件夹是共享的，一台服务器修改后，其它服务器
 *       本来也获取到最新脚本了，不应该再做脚本同步。
 *       
 *       切记，否则容易出现同步风暴，导致请求都被占用
 * 
 * 报文头说明：
 * 
 * 标识：_rsl_
 * 发起方服务器名： _rsl_src_server_
 * 配置信息版本号：_rsl_config_ver_
 * 
 * 
 * 报文格式：
 * 
 * <root server="">
 *   <group name="" ver="">
 *     <script id="" delete="1" ver="">
 *   </group>
 * </root>
 * 
 * -----------------------------------------------------------
 * 同步脚本数据逻辑：
 * 
 * 版本号规则： 版本号只针对不同群组的脚本信息。
 *          
 * 跨群组版本号 ver：自增数值
 * 
 * 先同步同群组信息，同步后再广播同步跨群组信息
 * 
 * -----------------------------------------------------------
 * 集群信息容器中的参数：
 * 
 * 
 * rsl_ver    同步次数
 * rsl_server 请求方服务器名
 * 
 * 2019-10-21 完成了正常情况下同步同群组脚本以及共享不同群组脚本的基本功能
 * 2019-10-23 修改了在测试时发现的问题
 * 2019-10-25 修改了集群还没准备好时，接收请求信息时处理报错的问题
 * 2019-10-30 因为ScriptLoader中删除脚本方法增加了删除原因参数，在这里也做了修改
 * 2020-03-16 增加了配置文件参数script_disabled_remote，可以禁用远程脚本路由功能
 * 2020-03-18 修改了避免同步风暴发生
 * 2020-03-20 修改了本机禁用远程脚本路由后，不再接收触发事件
 * 2020-07-05 版本号从整型序列号改为毫秒时间戳，因为有时因为修改脚本，导致当前服务
 *            器整体版本号比较大，后来重启当前服务后，版本从0开始，导致目标服务器不再更新版本号
 * 2020-07-14 增加了一些获取当前类中容器数据的方法，用来显示数据参考调试用
 * 2020-07-31 将一个日志从错误等级降为警告
 * 2020-08-08 修改了报文中服务器版本为空的错误
 *            屏蔽掉无用的异常信息
 * 
 * @author MBG
 * 2019年9月30日
 */
@ClassInfo({"2020-08-08 23:41","远程脚本加载器"})
@BeanInfo({"remotescriptloader"})
public class RemoteScriptLoader extends ABase implements IClusterTrigger {

	private ScriptLoader           sl              = null;              //类加载器
	private ClusterFilter          cf              = null;              //集群管理类
	private boolean                enabled         = false;             //是否启用远程脚本加载器
	private Map<String,String>     scriptGroupMap  = new HashMap<>();   //外部服务器脚本容器  key：脚本主键  value：群组主键 
	private Map<String,String>     scriptVerMap    = new HashMap<>();   //脚本版本容器 key:脚本主键 value:脚本版本号
	private SListMap<List<String>> groupScriptMap  = new SListMap<>();  //群组中的脚本序列（不包含当前服务器群组） key:群组名
	private Map<String,Long>       groupVerMap     = new HashMap<>();   //其它群组时间戳容器
	private long                   ver             = 0;                 //脚本信息变动计数器 当前时间毫秒值（用于版本控制）
	private SyncThread             syncThread      = null;              //同步线程
	private List<String[]>         delScriptList   = new ArrayList<>(); //删除脚本序列 0脚本主键 1删除原因信息
	
	/**
	 * 同步信息线程
	 * @author MBG
	 * 2019年10月11日
	 */
	private class SyncThread extends Thread {
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public SyncThread() {
			super("RemoteScriptLoader-SyncThread");
		}
		

		/**
		 * 覆盖方法
		 */
		@Override
        public void run() {
			try {
				while(true) {
					doSyncInfo(null);
					sleep(1000);
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 同步同群组数据
	 * @param ignoreServerName 忽略不发送的服务器名（存在该值时为接收到其它服务器信息，然后比对本机信息发现有变化时，推送给其它周边服务器，这时候为强制推送）
	 * @return  true同步工作执行完毕   false同步工作还需要继续
	 * 2019年10月11日
	 * @author MBG
	 */
	private void doSyncInfo(String ignoreServerName) {
		//报文头对象
		Map<String,String> header = new HashMap<>();
		header.put("rsl_ver",String.valueOf(ver));
		//本机的配置信息（有需要推送的服务器时在构造配置信息
		String configStr = null;
		
		/*
		 * 将本机配置信息推送到同群组其他服务器中
		 */
		
		List<ServerInfoVO> vos = cf.getServerInfoGroupOther();
		IViewHandler res; //返回信息对象
		for(ServerInfoVO vo:vos) {
			if(!vo.validSend || (ignoreServerName!=null && ignoreServerName.equals(vo.name))) {
				continue;
			}
			if(lon(vo.parameter("send_ver"))!=ver || ignoreServerName!=null) {
				if(configStr==null) {
					configStr = buildConfig();
				}
				info("\n\n------$$$$$  Begin Sync Local Script Info  $$$$$------ To Local Group:["+vo.group+"] Server:["+vo.name+"]\n\n");
				res = null;
				try {
					//准备推送当前服务器脚本信息
					res = sendConfig(vo,configStr);
				}catch(HttpException he) {
					warning("--RemoteScriptLoader-sendConfig-Exception1 Code:["+he.getStatusCode()+"] Message:["+he.getMessage()+"]  targetServer:["+vo.name+"] targetVer:["+vo.parameter("send_ver")+"]");
				}catch(Exception e) {
					//目标服务器可能重启导致抛异常，没必要抛异常
					//e.printStackTrace();
				}
				if(res!=null) {
					//标记已经发送过最新版本信息
					vo.getParameterMap().put("send_ver",String.valueOf(ver));
					parseReciveConfig(res,vo);
				}
			}
		}
		
		/*
		 * 将本机配置信息推送到其它群组服务器中
		 */
		String localGroup = cf.group(); //当前群组名
		//获取全部集群服务器信息序列
		SListMap<ServerInfoVO> serverList = cf.getServerInfo();
		for(ServerInfoVO vo:serverList){
			if(vo==null || vo.group.equals(localGroup) || vo.disabled || !vo.validSend || (ignoreServerName!=null && ignoreServerName.equals(vo.name))) {
				continue;
			}
			if(lon(vo.parameter("send_ver"))!=ver) {
				if(configStr==null) {
					configStr = buildConfig();
				}
				info("\n\n------$$$$$  Begin Sync Local Script Info  $$$$$------ To RemoteGroup:["+vo.group+"] Server:["+vo.name+"]\n\n");
				res = null;
				try {
					//准备推送当前服务器脚本信息
					res = sendConfig(vo,configStr);
				}catch(HttpException he) {
					warning("--RemoteScriptLoader-sendConfig-Exception2 Code:["+he.getStatusCode()+"] Message:["+he.getMessage()+"]  targetServer:["+vo.name+"] targetVer:["+vo.parameter("send_ver")+"]");
				}catch(Exception e) {
					//目标服务器可能重启导致抛异常，没必要抛异常
					//e.printStackTrace();
				}
				if(res!=null) {
					//标记已经发送过最新版本信息
					vo.getParameterMap().put("send_ver",String.valueOf(ver));
					parseReciveConfig(res,vo);
				}
			}
		}
	}
	
	/**
	 * 远程脚本加载器是否有效
	 * @return 远程脚本加载器是否有效
	 * 2019年10月11日
	 * @author MBG
	 */
	public boolean enabled() {
		return enabled;
	}
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public RemoteScriptLoader(ScriptLoader sl,ClusterFilter cf) {
		super();
		setBase(sl);
		this.sl = sl;
		this.cf = cf;
		if(cf!=null && !cf.disabled() && cf instanceof IBeanRegister) {
			((IBeanRegister)cf).regist(this);
		}
	}

	/**
	 * 集群管理类初始化后调用改方法
	 * @param cf 集群管理类
	 * 2017年3月29日
	 * @author MBG
	 */
	@Override
	public void clusterInited(ClusterFilter cf) {
		//cf 已经在构造函数中设置过来了，在这里无需再设置
		
		//是否禁用远程脚本路由
		String disabledStr = p("script_disabled_remote");
		if(disabledStr.length()>0) {
			enabled = !boo(disabledStr);
		}else {
			//标记有效
			enabled = true;
		}
		
		if(!enabled) {
			info("\n\n******* Remote Script Loader Set Disabled ********\n\n");
			return;
		}
		
		//标记计数器值为1
		ver = System.currentTimeMillis();
		
		//启动同步线程
		syncThread = new SyncThread();
		syncThread.start();
	}
	
	/**
	 * 尝试获取该脚本所在的集群分组名 （返回值存在null，强制转换为空字符串影响效率）
	 * @param scriptId 脚本主键
	 * @return         该脚本所在的集群分组名
	 * 2019年10月8日
	 * @author MBG
	 */
	public String serverGroup(String scriptId) {
		if(enabled) {
			return scriptGroupMap.get(scriptId);
		}
		return null;
	}

	/**
	 * 首次完成发送心跳到某个目标服务器后执行该方法
	 * @param siVO   目标服务器信息类
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
	public void validSended(ServerInfoVO siVO) {}

	/**
	 * 首次完成接收目标心跳后执行该方法
	 * @param siVO   发送方服务器信息类
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
	public void validReceived(ServerInfoVO siVO) {}

	/**
	 * 返回触发响应主键
	 * @return 触发响应主键
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
	public String triggerKey() {
		return "_rsl_";
	}

	/**
	 * 执行发送本地配置信息
	 * @param  siVO          目标服务器信息类
	 * @param  configContent 配置信息内容文本
	 * @throws 发送失败异常
	 * @return
	 * 2019年10月8日
	 * @author MBG
	 */
	private IViewHandler sendConfig(ServerInfoVO siVO,String configContent) throws Exception {
		IViewHandler res = null; //构建返回值
		InputStream  is  = null; //构建读入流
		info("--- Begin Send Local Config To :["+siVO.name+"] Group:["+siVO.group+"]");
		//请求头
		Map<String,String> header = new HashMap<>();
		header.put("rsl_server",cf.name());
		try {
			is  = cf.triggerCall(siVO,configContent,header,1,this);
			res = FNodeHandler.getNodeHandler(is);
		}finally {
			try {
				is.close();
			}catch(Exception e) {}
		}
		return res;
	}
	
	/**
	 * 生成需要发送的配置信息报文
	 * @param targetSiVO 目标服务器信息类
	 * @return 配置信息报文
	 * 2019年10月11日
	 * @author MBG
	 */
	private String buildConfig() {
		/* <root server="">
		 *   <group name="" ver="">
		 *     <script id="" delete="1" enabled="1" ver="" />
		 *   </group>
		 * </root>
		 */
		//构建返回值
		StringBuffer reSbf = new StringBuffer();
		reSbf
		  .append("<root server=\"").append(cf.name()).append("\">")
		  .append("<group name=\"")
		     .append(cf.group()).append("\"  ver=\"")
		     .append(ver).append("\">");
		
		//获取全部脚本主键序列
		List<String> scriptIdList = sl.getScriptIdList();
		ScriptVO sVO; //脚本信息类
		String   ena;
		for(String id:scriptIdList) {
			sVO = sl.getScriptInfo(id);
			if(sVO==null || sVO.nativeScript) {
				continue;
			}
			if(sVO.disabled || sVO.hasError()) {
				ena = "0";
			}else {
				ena = "1";
			}
			reSbf
			  .append("<script id=\"")
			  .append(id).append("\" enabled=\"")
			  .append(ena).append("\" ver=\"")
			  .append(sVO.sourceFileVer).append("\"/>");
		}
		String[] delInfo; //删除的脚本主键
		while(delScriptList.size()>0) {
			delInfo = delScriptList.remove(0);
			reSbf
			  .append("<script id=\"")
			  .append(delInfo[0])
			  .append("\" delete=\"1\"")
			  .append(" message=\"")
			  .append(delInfo[1]==null?"":e64(delInfo[1]))
			  .append("\" />");
		}
		reSbf.append("</group>");
		
		Long         groupVer;        //其它群组版本号
		List<String> groupScriptList; //其它群组中的脚本主键序列
		//放入其他群组脚本数据
		for(String group:groupScriptMap.keys()) {
			groupScriptList = groupScriptMap.get(group);
			if(groupScriptList==null) {
				continue;
			}
			groupVer = groupVerMap.get(group);
			
			reSbf
				.append("<group name=\"")
				.append(group)
				.append("\" ver=\"")
				.append(groupVer==null?"0":groupVer)
				.append("\">");
			
			for(String id:groupScriptList) {
				reSbf
				  .append("<script id=\"")
				  .append(id).append("\" ver=\"")
				  .append(str(scriptVerMap.get(id))).append("\"/>");
			}
			reSbf.append("</group>");
		}
		reSbf.append("</root>");
		return reSbf.toString();
	}
	
	/**
	 * 本地脚本发生变化
	 * 2019年10月15日
	 * @author MBG
	 */
	public void localChanged() {
		if(!enabled) {
			//禁用集群脚本路由同步
			return;
		}
		info("---------------------------localChanged-----------");
		ver = System.currentTimeMillis();
	}
	
	/**
	 * 脚本被删除触发事件
	 * @param scriptId 被删除的脚本主键
	 * 2019年10月19日
	 * @author MBG
	 */
	public void deletedChange(String scriptId,String message) {
		if(!enabled) {
			//禁用集群脚本路由同步
			return;
		}
		delScriptList.add(new String[] {scriptId,message});
		info("-----------------------------deletedChange------------------------");
		ver = System.currentTimeMillis();
	}
	
	/**
	 * 同步其他分组信息
	 * @param data
	 * 2019年10月15日
	 * @author MBG
	 */
	private synchronized boolean syncOtherGroup(IViewHandler data) {
		if(data==null) {
			return false;
		}
		String groupName = data.a("name"); //群组名
		if(groupName.length()<1) {
			return false;
		}
		long gVer = lon(data.a("ver")); //目标版本号
		if(gVer<1) {
			warning("---The RemoteGroup:["+groupName+"] Not Find The Ver Info");
		}
		if(gVer<lon(groupVerMap.get(groupName))) {
			//版本相同，无需同步
			return false;
		}
		//更新其他群组版本号
		groupVerMap.put(groupName,gVer);
		//上一次记录的群组中的脚本主键序列
		List<String> upsList = new ArrayList<String>();
		//当前群组中的脚本主键序列
		List<String> gsList  = groupScriptMap.get(groupName);
		if(gsList==null) {
			gsList  = new ArrayList<String>();
			groupScriptMap.put(groupName,gsList);
		}else {
			upsList.addAll(gsList);
		}
		
		//本地脚本主键序列
		List<String> localScriptIdList = sl.getScriptIdList();
		
		List<IViewHandler> sList = data.cnn("script");  //获取脚本信息序列
		String             id;                          //脚本主键元素
		String             sVer;                        //远程脚本版本
		for(IViewHandler ele:sList) {
			id   = ele.a("id");
			sVer = ele.a("ver");
			if(boo(ele.a("delete"))) {
				continue;
			}
			//如果本地也有远程的这个脚本，不再自动路由执行远程的这个脚本
			if(id.length()<1 || sVer.length()<1 || localScriptIdList.contains(id)) {
				continue;
			}
			upsList.remove(id);
			
			if(!gsList.contains(id)) {
				gsList.add(id);
			}
			scriptGroupMap.put(id,groupName);
			scriptVerMap.put(id,sVer);
		}
		//处理上次还有，但本次没有了的脚本主键
		for(String delId:upsList) {
			gsList.remove(delId);
			scriptGroupMap.remove(delId);
			scriptVerMap.remove(delId);
		}
		return true;
	}
	
	
	/**
	 * 同步当前群组中的脚本信息
	 * @param data 来自同群组其它服务器的信息
	 * @return true本机脚本信息发生了变化  false本机脚本信息没有变化
	 * 2019年10月14日
	 * @author MBG
	 */
	private synchronized boolean syncCurrentGroup(IViewHandler data,ServerInfoVO siVO) {
		if(data==null || data.isEmpty() || lon(data.a("ver"))<ver) {
			return false;
		}
		//是否需要更新当前服务器脚本版本
		boolean needUpdateLocalVer = false;
		Long totalVer = lon(data.a("ver")); //服务器总版本号
		
		//标记处理过该服务器的版本信息
		siVO.getParameterMap().put("rsl_ver",String.valueOf(totalVer));
		
		List<IViewHandler> sList = data.cnn("script");  //获取脚本信息序列
		String             id;                          //脚本主键元素
		String             sVer;                        //远程脚本版本
		boolean            del;                         //远程脚本删除标识
		ScriptVO           sVO;                         //本地脚本信息类
		for(IViewHandler ele:sList) {
			id   = ele.a("id");
			sVer = ele.a("ver");
			del  = boo(ele.a("delete"));
			sVO  = sl.getScriptInfo(id,false);
			if(sVO==null) {
				if(del) {
					//正常，本地已经删除
					continue;
				}
				sVO = getRemoteScriptInfo(id,siVO);
				if(sVO!=null) {
					sl.nativeSaveSource(sVO);
				}
				needUpdateLocalVer = true;
				continue;
			}
			if(del) {
				//删除当前脚本
				sVO.modifyContent = d64(ele.a("message")); //设置删除原因信息
				sl.nativeDeleteScript(sVO);
				needUpdateLocalVer = true;
				continue;
			}
			if(sVO.nativeScript) {
				//不该存在该情况，不处理内置脚本
				continue;
			}
			if(sVO.sourceFileVer==null || sVO.sourceFileVer.length()<1) {
				//不应该存在这种情况，肯定会有版本号
				error("----The Local Server Script:["+sVO.id+"] Not Find The VER Info");
				continue;
			}
			if(sVer.length()<1) {
				//不应该存在这种情况，肯定会有版本号
				error("---- The Remote Server:["+siVO.name+"] Script:["+id+"] Not Find The VER Info");
				continue;
			}
			if(sVer.equals(sVO.sourceFileVer)) {
				//版本相同
				continue;
			}
			if(SDate.millisecondOf(sVer)>SDate.millisecondOf(sVO.sourceFileVer)) {
				//远程的比本地的新
				sVO = getRemoteScriptInfo(id,siVO);
				if(sVO!=null) {
					sl.nativeSaveSource(sVO);
				}
				needUpdateLocalVer = true;
			}
		}
		if(needUpdateLocalVer && ver<totalVer) {
			info("-------------------needUpdateLocalVer  local:["+ver+"] totalVer:["+totalVer+"]");
			ver = totalVer;
			return true;
		}
		return false;
	}
	
	/**
	 * 远程获取脚本信息对象
	 * @param scriptId  脚本信息主键
	 * @param siVO      目标服务器信息对象
	 * @return          未保存的脚本信息容器
	 * 2019年10月14日
	 * @author MBG
	 */
	private ScriptVO getRemoteScriptInfo(String scriptId,ServerInfoVO siVO) {
		//报文头对象
		Map<String,String> header = new HashMap<String,String>();
		header.put("rsl_script_get","1");     //标记获取脚本内容请求
		header.put("rsl_script_id",scriptId); //要获取脚本内容的代码
		ScriptVO    reVO  = null; //构建返回值
		InputStream is    = null; //返回信息读入流
		try {
			is = cf.triggerCall(siVO,null,header,0,this);
			IViewHandler res  = FNodeHandler.getNodeHandler(is);
			reVO              = new ScriptVO(sl,res);
			reVO.isSyncSubmit = true; //标记同步脚本方式更新
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e) {}
		}
		return reVO;
	}

	/**
	 * 响应发起服务器请求
	 * @param siVO   发起服务器信息类
	 * @param data   请求参数对象
	 * @return       返回值
	 * 2019年10月8日
	 * @author MBG
	 */
	@Override
	public void triggerRecive(ServerInfoVO siVO,IRequest req,IResponse resp) {
		if(!cf.clusterMode() || !enabled) {
			//集群服务还没准备好，无法接收处理消息 或 禁用集群脚本路由同步
			return;
		}
		//判断是否为同群组远程获取本地指定脚本信息
		if(cf.group().equals(siVO.group) && "1".equals(str(req.getHeader("rsl_script_get")))) {
			//需要获取的脚本主键
			String scriptId = str(req.getHeader("rsl_script_id"));
			info("----triggerRecive Get ScriptID:["+scriptId+"] from:["+siVO.name+"] group:["+siVO.group+"]");
			try {
				if(scriptId.length()<1) {
					resp.sendError(404,"Not Found The ScriptID For Remote Get");
				}else {
					//获取指定脚本信息类
					ScriptVO sVO = sl.getScriptInfo(scriptId);
					if(sVO==null) {
						resp.sendError(500,"The ScriptID:["+scriptId+"] Not Find For Remote Get. Request From:["+siVO.name+"]");
					}else {
						resp.getOutputStream().write(sVO.toXml().getBytes(StandardCharsets.UTF_8));
					}
				}
			}catch(Exception e) {
				e.printStackTrace();
				try {
					resp.sendError(500,e.toString());
				}catch(Exception e2) {}
			}
			return;
		}
		info("----triggerRecive Sync Config from:["+siVO.name+"] group:["+siVO.group+"]");
		try {
			//获取提交过来的报文
			IViewHandler data = FNodeHandler.getNodeHandler(req.getInputStream());
			//解析接收到的数据
			parseReciveConfig(data,siVO);
			
			/*
			收到同步事件后，马上执行群发，会造成同步风暴，无休止的同步，最后线程用尽锁死
			if(parseReciveConfig(data,siVO)) {
				//本地接收到的其它群组版本信息发生变化，通知可以能访问到的群组
				doSyncInfo(siVO.name);
			}
			*/
			//返回当前集群脚本配置信息
			resp.getOutputStream().write(buildConfig().getBytes(StandardCharsets.UTF_8));
		}catch(Exception e) {
			error("triggerRecive",e);
			//e.printStackTrace(); //先显示简单的错误信息
		}
	}
	
	
	/**
	 * 获取群组中的脚本序列（不包含当前服务器群组） key:群组名
	 * @return 群组中的脚本序列
	 * 2020年7月14日
	 * @author MBG
	 */
	public SListMap<List<String>> getGroupScriptMap(){
		return groupScriptMap;
	}
	
	/**
	 * 获取脚本版本容器 key:脚本主键 value:脚本版本号
	 * @return 脚本版本容器
	 * 2020年7月14日
	 * @author MBG
	 */
	public Map<String,String> getScriptVerMap(){
		return scriptVerMap;
	}
	
	/**
	 * 获取其它群组时间戳容器
	 * @return 其它群组时间戳容器
	 * 2020年7月14日
	 * @author MBG
	 */
	public Map<String,Long> getGroupVerMap(){
		return groupVerMap;
	}
	
	/**
	 * 获取当前系统脚本信息变动计数器 当前时间毫秒值（用于版本控制）
	 * @return 当前系统脚本信息变动计数器
	 * 2020年7月14日
	 * @author MBG
	 */
	public long getVer() {
		return ver;
	}
	
	/**
	 * 解析接收到的数据
	 * @param data   数据对象
	 * @param siVO   来源服务器信息对象
	 * @return       本地版本是否发生了变化
	 * 2019年10月18日
	 * @author MBG
	 */
	private boolean parseReciveConfig(IViewHandler data,ServerInfoVO siVO) {
		boolean hasChange     = false;      //本地脚本是否发生了变化
		String localGroupName = cf.group(); //当前群组名
		//获取分组配置信息序列
		List<IViewHandler> groupList = data.cnn("group");
		String groupName; //群组名
		for(IViewHandler groupVh:groupList) {
			groupName = groupVh.a("name");
			if(groupName.equals(localGroupName)) {
				if(localGroupName.equals(siVO.group)) {
					//同群组集群
					if(syncCurrentGroup(groupVh,siVO)) {
						hasChange = true;
					}
				}
				continue;
			}
			//跨群组集群
			if(syncOtherGroup(groupVh)) {
				hasChange = true;
			}
		}
		return hasChange;
	}
}
